﻿using System;
using System.Threading;

using BrightSword.Crucible;
using BrightSword.SwissKnife;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Tests.BrightSword.SwissKnife
{
    [TestClass]
    public class TimingHelperTests
    {
        [Flags]
        public enum ObservedActions
        {
            None,

            Started,

            Succeeded,

            Completed,

            FailedWithException,

            CorrectSuccess = Started | Succeeded | Completed,

            CorrectFailure = Started | FailedWithException | Completed
        };

        [TestMethod]
        public void Test_TimeShouldExecuteTheActionProvided_Failure()
        {
            Action failingAction = () =>
                                   {
                                       throw new ArgumentNullException();
                                   };

            ExceptionHelper.ExpectException<ArgumentNullException>(() => failingAction.Time());
        }

        [TestMethod]
        public void Test_TimeShouldExecuteTheActionProvided_Success()
        {
            Action succeedingAction = () => Thread.Sleep(100);
            var observer = new TestObserver();
            var timespan = succeedingAction.Time(observer);
            Assert.AreEqual(ObservedActions.CorrectSuccess,
                            observer.ActionsObserved);

            Assert.IsTrue(timespan.TotalMilliseconds >= 10);
        }

        [TestMethod]
        public void Test_TimeShouldExecuteTheFunctionProvided_Failure()
        {
            Func<DateTime> failingFunc = () =>
                                         {
                                             throw new ArgumentNullException();
                                         };
            var observer = new TestObserver();

            ExceptionHelper.ExpectException<ArgumentNullException>(() => failingFunc.Time(observer));
            Assert.AreEqual(ObservedActions.CorrectFailure,
                            observer.ActionsObserved);
        }

        [TestMethod]
        public void Test_TimeShouldExecuteTheFunctionProvided_Success()
        {
            Func<int> succeedingFunc = () => 42;
            Assert.AreEqual(42,
                            succeedingFunc.Time()
                                          .Result);
        }

        private class TestObserver : ITimedOperationObserver
        {
            private ObservedActions _actionsObserved = ObservedActions.None;

            public ObservedActions ActionsObserved
            {
                get
                {
                    return _actionsObserved;
                }
            }

            public void Started()
            {
                _actionsObserved |= ObservedActions.Started;
            }

            public void Succeeded()
            {
                _actionsObserved |= ObservedActions.Succeeded;
            }

            public void Completed(TimeSpan elapsedTimeFromStart)
            {
                _actionsObserved |= ObservedActions.Completed;
            }

            public void FailedWithException(Exception exception)
            {
                _actionsObserved |= ObservedActions.FailedWithException;
            }

            public void Reset()
            {
                _actionsObserved = ObservedActions.None;
            }
        }
    }
}